Skip to main content

5: Issuing a Credential

Now the Issuer issues credentials

Now that a connection has been made between the Issuer and the Holder, the Issuer can send the credential to the Holder using the connection_id from the Issuer's perspective.

Again both tenants can listen for events on the topic: credentials

Issuing a Non-Revocable Credential

To issue a non-revocable credential, the issuer needs to send a POST request to the appropriate endpoint. Below is an example of how to issue a non-revocable credential:

curl -X 'POST' \
'https://cloudapi.test.didxtech.com/tenant/v1/issuer/credentials' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'x-api-key: tenant.<Issuer token>' \
-d '{
"type": "indy",
"indy_credential_detail": {
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"attributes": {
"Name": "Alice",
"Surname": "Holder",
"Age": "25"
}
},
"connection_id": "c78f9423-370e-4800-a48e-962456083943",
"protocol_version": "v2"
}'

Response:

{
"attributes": {
"Name": "Alice",
"Surname": "Holder",
"Age": "25"
},
"connection_id": "c78f9423-370e-4800-a48e-962456083943",
"created_at": "2023-11-20T09:59:29.820002Z",
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"credential_exchange_id": "v2-f126edb7-1ac1-43a3-bf1f-60b8feae4701",
"did": null,
"error_msg": null,
"protocol_version": "v2",
"role": "issuer",
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0",
"state": "offer-sent",
"thread_id": "9ceeb941-4ebd-42ec-9ffc-ea0b7fe39722",
"type": "indy",
"updated_at": "2023-11-20T09:59:29.820002Z"
}

As you can see from the state, an offer has now been sent, and needs to be accepted/requested by the holder.

Note that the issuer will now have what's called a credential exchange record in state: offer-sent. Pending exchange records can be viewed by calling GET /v1/issuer/credentials, and completed credential exchange records are deleted by default, but can be preserved by adding an optional save_exchange_record=True field to the request.

Issuing a Revocable Credential

Credentials that support revocation are issued in the same way as described above, but there is additional information that the issuer needs to keep track of.

There is a webhook event topic that an issuer can subscribe to called "issuer_cred_rev". The "issuer_cred_rev" event has information on the issued credential and how it is connected to the revocation registries and some metadata.

This event will fire under two circumstances:

  • Once a credential (with revocation support) is issued.
  • And when a credential is revoked.

The state of the event will correspond with these events, i.e. first it will be "issued" and after revocation it will be "revoked".

Let's take a look at an example event:

{
"wallet_id": "5df42bab-6719-4c8a-a615-8086435d4de4",
"topic": "issuer_cred_rev",
"origin": "tenant faber",
"group_id": "GroupA",
"payload": {
"created_at": "2024-04-30T08:51:18.177543Z",
"cred_def_id": "QrMaE11MnC6zjKNY1pxbq8:3:CL:8:Epic",
"cred_ex_id": "af4bad3f-3fcc-47ab-85e6-24224dcb2779",
"cred_ex_version": "2",
"cred_rev_id": "2",
"record_id": "57bd9c72-fa29-4f65-bd89-4e241471073a",
"rev_reg_id": "QrMaE11MnC6zjKNY1pxbq8:4:QrMaE11MnC6zjKNY1pxbq8:3:CL:8:Epic:CL_ACCUM:53462552-d716-4b0b-8b5c-914a3574d2c4",
"state": "issued",
"updated_at": "2024-04-30T08:51:18.177543Z"
}
}

Taking a look at the payload, there are a few fields we are interested in:

  • "cred_def_id" or credential definition id: The same id the issuer gets when creating a credential definition (also used when issuing credentials).
  • "cred_ex_id" or credential exchange id: This is the same exchange id that can be found in the credential exchange record, the protocol version is just stripped here.
  • "cred_rev_id" or credential revocation id: This is the id that ties the credential to the revocation registry.
  • "rev_reg_id" or revocation registry id: This is the id of the revocation registry the credential was issued against.

The "issuer_cred_rev" event is not the only place this data is available. Under the issuer API, there is an endpoint: GET /v1/issuer/credentials/revocation/record. This endpoint will return the payload object of the "issuer_cred_rev" event:

{
"created_at": "2024-04-30T08:51:18.177543Z",
"cred_def_id": "QrMaE11MnC6zjKNY1pxbq8:3:CL:8:Epic",
"cred_ex_id": "af4bad3f-3fcc-47ab-85e6-24224dcb2779",
"cred_ex_version": "2",
"cred_rev_id": "2",
"record_id": "57bd9c72-fa29-4f65-bd89-4e241471073a",
"rev_reg_id": "QrMaE11MnC6zjKNY1pxbq8:4:QrMaE11MnC6zjKNY1pxbq8:3:CL:8:Epic:CL_ACCUM:53462552-d716-4b0b-8b5c-914a3574d2c4",
"state": "issued",
"updated_at": "2024-04-30T08:51:18.177543Z"
}

This endpoint has three query parameters:

  • "credential_exchange_id"
  • "credential_revocation_id"
  • "revocation_registry_id"

If "credential_exchange_id" is not provided, both the "credential_revocation_id" and "revocation_registry_id" must be provided.

So with the "credential_exchange_id" (from the credential exchange record), an issuer can get the relevant data needed to revoke the credential associated with the exchange id.

[!NOTE] Issuers take note: The most important thing the issuer needs to keep track of is how their credential exchange ids map to credentials they have issued to holders. Without knowing how their exchange ids map to their holders, they won't know which credential to revoke.

Holder requests credential

Now the Holder needs to respond to the credential sent to them. Below the Holder is getting all their connections. We are doing this to get the connection_id of the connection to the issuer. This connection_id can also be gotten from the SSE events.

curl -X 'GET' \
'https://cloudapi.test.didxtech.com/tenant/v1/connections' \
-H 'accept: application/json' \
-H 'x-api-key: tenant.<Holder token>'

Response:

[
{
"alias": "Holder <> Issuer",
"connection_id": "ac3b0d56-eb33-408a-baeb-0370164d47ae",
"connection_protocol": "connections/1.0",
"created_at": "2023-11-20T09:56:41.437966Z",
"error_msg": null,
"invitation_key": "91ZNSpDgVoV12kHcmUqyp1JmGeKE7oGi9NFd2WMzKt4X",
"invitation_mode": "once",
"invitation_msg_id": "6a86e6c7-af25-4e5d-87fe-b42f559b13b9",
"my_did": "MYhLew4uq58mou8SCTNFYp",
"state": "completed",
"their_did": "6wMwbinRJ5XKyBJKm7P5av",
"their_label": "Demo Issuer",
"their_public_did": null,
"their_role": "inviter",
"updated_at": "2023-11-20T09:56:41.656141Z"
}
]

Note the connection_id in the above response.

The Holder can then find the credentials offered to them on this connection_id by calling /v1/issuer/credentials with the optional connection_id query parameter:

curl -X 'GET' \
'https://cloudapi.test.didxtech.com/tenant/v1/issuer/credentials?connection_id=ac3b0d56-eb33-408a-baeb-0370164d47ae' \
-H 'accept: application/json' \
-H 'x-api-key: tenant.<Holder token>'

Response:

[
{
"attributes": {
"Name": "Alice",
"Surname": "Holder",
"Age": "25"
},
"connection_id": "ac3b0d56-eb33-408a-baeb-0370164d47ae",
"created_at": "2023-11-20T09:59:29.868946Z",
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"credential_exchange_id": "v2-c492cec7-2f2d-4d5f-b839-b57dcd8f8eee",
"did": null,
"error_msg": null,
"protocol_version": "v2",
"role": "holder",
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0",
"state": "offer-received",
"thread_id": "9ceeb941-4ebd-42ec-9ffc-ea0b7fe39722",
"type": "indy",
"updated_at": "2023-11-20T09:59:29.868946Z"
}
]

Note the credential_exchange_id and state: offer-received. Additionally, note that the holder and the issuer have different credential_exchange_id references for the same credential exchange interaction.

The Holder can now request the credential, using the credential_exchange_id from the above response, by calling /v1/issuer/credentials/{credential_exchange_id}/request:

curl -X 'POST' \
'https://cloudapi.test.didxtech.com/tenant/v1/issuer/credentials/v2-c492cec7-2f2d-4d5f-b839-b57dcd8f8eee/request' \
-H 'accept: application/json' \
-H 'x-api-key: tenant.<Holder token>' \
-d ''

Response:

{
"attributes": {
"Name": "Alice",
"Surname": "Holder",
"Age": "25"
},
"connection_id": "ac3b0d56-eb33-408a-baeb-0370164d47ae",
"created_at": "2023-11-20T09:59:29.868946Z",
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"credential_exchange_id": "v2-c492cec7-2f2d-4d5f-b839-b57dcd8f8eee",
"did": null,
"error_msg": null,
"protocol_version": "v2",
"role": "holder",
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0",
"state": "request-sent",
"thread_id": "9ceeb941-4ebd-42ec-9ffc-ea0b7fe39722",
"type": "indy",
"updated_at": "2023-11-20T10:02:02.708045Z"
}

The holder request has been sent, and an automated workflow will transition the credential to being stored in the holder's wallet.

We can listen on SSE and wait for state to be done on the topic: credentials

{
"wallet_id": "7bb24cc8-2e56-4326-9020-7870ad67b257",
"topic": "credentials",
"origin": "multitenant",
"payload": {
"attributes": null,
"connection_id": "ac3b0d56-eb33-408a-baeb-0370164d47ae",
"created_at": "2023-11-20T09:59:29.868946Z",
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"credential_exchange_id": "v2-c492cec7-2f2d-4d5f-b839-b57dcd8f8eee",
"did": null,
"error_msg": null,
"protocol_version": "v2",
"role": "holder",
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0",
"state": "done",
"thread_id": "9ceeb941-4ebd-42ec-9ffc-ea0b7fe39722",
"type": "indy",
"updated_at": "2023-11-20T10:02:03.043100Z"
}
}

Once the state is done, the credential will be in the Holder's wallet. We can list the credential in the wallet by doing the following call as the Holder:

curl -X 'GET' \
'https://cloudapi.test.didxtech.com/tenant/v1/wallet/credentials' \
-H 'accept: application/json' \
-H 'x-api-key: tenant.<Holder token>'

Response:

{
"results": [
{
"attrs": {
"Surname": "Holder",
"Name": "Alice",
"Age": "25"
},
"cred_def_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"cred_rev_id": null,
"referent": "86dfb6ef-1ff5-41fd-977b-092a1d97e20b",
"rev_reg_id": null,
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0"
}
]
}

Note: the credential has no reference to a credential_exchange_id. In the wallet context, the referent is the credential id, and is different from the credential_exchange_id used during the credential exchange.

Hooray! 🥳🎉 The holder now has a credential!

Next: Create connection with Verifier